#include <ir/inst.h>

#define VISITOR riscv::Visitor

namespace ir {

  VISIT(VISITOR, InstImm);

  VISIT(VISITOR, InstAlloc);
  VISIT(VISITOR, InstLoad);
  VISIT(VISITOR, InstStore);
  VISIT(VISITOR, InstArrayAlloc);
  VISIT(VISITOR, InstArrayLoad);
  VISIT(VISITOR, InstArrayStore);
  VISIT(VISITOR, InstGlobalAlloc);
  VISIT(VISITOR, InstGlobalLoad);
  VISIT(VISITOR, InstGlobalStore);

  VISIT(VISITOR, InstI32ToF32);
  VISIT(VISITOR, InstF32ToI32);

  VISIT(VISITOR, InstCall);
  VISIT(VISITOR, InstRet);

  VISIT(VISITOR, InstAdd);
  VISIT(VISITOR, InstSub);
  VISIT(VISITOR, InstMul);
  VISIT(VISITOR, InstDiv);

  VISIT(VISITOR, InstJmp);
  VISIT(VISITOR, InstBne);
  VISIT(VISITOR, InstBeq);

  Inst::~Inst() {}

  void InstImm::print_get(std::string & str) {
    str = this->reg->get_name()
      + "(" + enum_name(this->reg->get_type()) + ")"
      + " = imm("
      + enum_name(this->type) + ") ";
    switch(this->type) {
    case VarType::i32:
      str += std::to_string(this->int_number);
      break;
    case VarType::f32:
      str += std::to_string(this->float_number);
      break;
    default:
      Panic("type can't be %s", enum_name(this->type).c_str());
    }
  }

  void InstAlloc::print_get(std::string & str) {
    str = this->reg->get_name()
      + "(" + enum_name(this->reg->get_type()) + ")"
      + " = alloc " + std::to_string(this->size);
  }

  void InstStore::print_get(std::string & str) {
    str = std::string("store ") + this->rd->get_name() + ", "
      + this->src->get_name();
  }

  void InstLoad::print_get(std::string & str) {
    str = this->rd->get_name()
      + "(" + enum_name(this->rd->get_type()) + ")"
      + " = load "
      + this->src->get_name();
  }

  void InstArrayAlloc::print_get(std::string & str) {
    str = this->reg->get_name()
      + "(" + enum_name(this->reg->get_type()) + ")"
      + " = array_alloc "
      + std::to_string(this->size)
      + "x" + std::to_string(this->count);
  }

  void InstArrayStore::print_get(std::string & str) {
    str = std::string("array_store ")
      + this->rd->get_name()
      + "[" + this->index->get_name() + "]" + ", "
      + this->src->get_name();
  }

  void InstArrayLoad::print_get(std::string & str) {
    str = this->rd->get_name()
      + "(" + enum_name(this->rd->get_type()) + ")"
      + " = " + this->src->get_name()
      + "[" + this->index->get_name() + "]";
  }

  void InstGlobalAlloc::print_get(std::string & str) {
    str = this->global_reg->get_name()
      + "(" + enum_name(this->global_reg->get_type()) + ")"
      + " = imm"
      + "(" + enum_name(this->type) + ")"
      + " " + std::to_string(this->int_number);
  }

  void InstGlobalStore::print_get(std::string & str) {
    str = std::string("global_store ")
      + this->global_rd->get_name()
      + ", " + this->src->get_name();
  }

  void InstGlobalLoad::print_get(std::string & str) {
    str = this->rd->get_name()
      + "(" + enum_name(this->rd->get_type()) + ")"
      + " = global_load "
      + this->global_src->get_name();
  }

  void InstI32ToF32::print_get(std::string & str) {
    str = this->rd->get_name()
      + "(" + enum_name(this->rd->get_type()) + ")"
      + " = i32_to_f32 " + this->src->get_name();
  }

  void InstF32ToI32::print_get(std::string & str) {
    str = this->rd->get_name()
      + "(" + enum_name(this->rd->get_type()) + ")"
      + " = f32_to_i32 " + this->src->get_name();
  }

  void InstCall::print_get(std::string & str) {
    str = this->rd->get_name()
      + " = call " + this->func->get_name();
    str += "(";
    for(unsigned long i = 0; i < this->args.size(); ++i) {
      str += this->args.at(i)->get_name();
      if(i != this->args.size()-1) {
        str += ", ";
      }
    }
    str += ")";
  }

  void InstRet::print_get(std::string & str) {
    str = std::string("ret");
    if(this->type == VarType::none) {
      str += "(none) nullptr";
    } else {
      str += "(" + enum_name(this->type) + ")"
      + " " + this->reg->get_name();
    }
  }

  void InstAdd::print_get(std::string & str) {
    str = this->rd->get_name()
      + "(" + enum_name(this->rd->get_type()) + ")"
      + " = " + "add "
      + this->src1->get_name() + ", " + this->src2->get_name();
  }

  void InstSub::print_get(std::string & str) {
    str = this->rd->get_name()
      + "(" + enum_name(this->rd->get_type()) + ")"
      + " = " + "sub "
      + this->src1->get_name() + ", " + this->src2->get_name();
  }

  void InstMul::print_get(std::string & str) {
    str = this->rd->get_name()
      + "(" + enum_name(this->rd->get_type()) + ")"
      + " = " + "mul "
      + this->src1->get_name() + ", " + this->src2->get_name();
  }

  void InstDiv::print_get(std::string & str) {
    str = this->rd->get_name()
      + "(" + enum_name(this->rd->get_type()) + ")"
      + " = " + "div "
      + this->src1->get_name() + ", " + this->src2->get_name();
  }

  void InstJmp::print_get(std::string & str) {
    str = std::string("jmp ") + this->block->get_name();
  }

  void InstBne::print_get(std::string & str) {
    str = std::string("bne ") + this->src1->get_name()
      + ", " + this->src2->get_name() + ", "
      + this->block->get_name();
  }

  void InstBeq::print_get(std::string & str) {
    str = std::string("beq ") + this->src1->get_name()
      + ", " + this->src2->get_name() + ", "
      + this->block->get_name();
  }
}
